/*
 * Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package blms.facade;

import static blms.util.ValidatorUtil.isBlankOrNull;
import static blms.util.ValidatorUtil.isInteger;

import java.awt.Stroke;
import java.io.File;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Date;

import blms.domain.HandicapContext;
import blms.domain.Joining;
import blms.domain.League;
import blms.domain.LeagueContext;
import blms.domain.Match;
import blms.domain.User;
import blms.exception.LeagueDoesNotExistException;
import blms.exception.MatchDoesNotExistException;
import blms.exception.RestrictionException;
import blms.exception.SyntaxErrorException;
import blms.exception.UnknownAttributeException;
import blms.exception.UnknownVariableException;
import blms.exception.UserDoesNotExistException;
import blms.exception.ValidationException;
import blms.repository.LeagueRepository;
import blms.repository.MatchRepository;
import blms.repository.MatchupTableRepository;
import blms.repository.UserRepository;
import blms.repository.infile.InFileMatchupRepository;
import blms.repository.inmemory.InMemoryLeagueRepository;
import blms.repository.inmemory.InMemoryMatchRepository;
import blms.repository.inmemory.InMemoryUserRepository;
import blms.services.LeagueService;
import blms.services.MatchService;
import blms.services.MatchupService;
import blms.services.UserService;
import blms.services.internal.LeagueServiceImpl;
import blms.services.internal.MatchServiceImpl;
import blms.services.internal.MatchupServiceImpl;
import blms.services.internal.UserServiceImpl;
import blms.util.BLMSMessages;
import blms.util.LocalizedDataFormat;
import blms.util.ReflectionHelper;
import blms.util.ValidatorUtil;
import blms.util.expression.AntlrExpression;

/**
 * This class is the central point of the BLMS system, it is responsible to create
 * the components and wire them. Also, it could be seen like a system facade to the
 * components.
 * 
 * @author João Arthur
 * @author Jonhnny Weslley
 * @author Thiago Emmanuel
 * @version 1.00, 21/04/2008
 * @since 1.0
 */
public class BLMS {

	private final UserRepository users = new InMemoryUserRepository();
	private final LeagueRepository leagues = new InMemoryLeagueRepository();
	private final MatchRepository matches = new InMemoryMatchRepository();
	private final MatchupTableRepository matchups = new InFileMatchupRepository();

	// services
	private final UserService userService = createUserService();
	private final LeagueService leagueService = createLeagueService();
	private final MatchService matchService = createMatchService();
	private final MatchupService matchupService = createMatchupService();
	private final Date today;

	
	public BLMS() {
		
		String separator = File.separator;
		
		// add all tables in the repository
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-150.txt",150);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-140.txt",140);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-130.txt",130);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-120.txt",120);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-110.txt",110);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-100.txt",100);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-90.txt",90);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-80.txt",80);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-70.txt",70);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-60.txt",60);
		this.matchups.addTable("resources"+separator+"tables"+separator+"table-50.txt",50);
		
		this.today = new Date();
	}
	
	/**
	 * Returns the <code>UserService</code> component.
	 * 
	 * @return
	 * 			the <code>UserService</code> component.
	 */
	public UserService getUserService() {
		return userService;
	}

	private MatchupService createMatchupService() {
		return new MatchupServiceImpl(matchups);
	}
	
	public MatchupService getMatchupService() {
		return this.matchupService;
	}

	/**
	 * Returns the <code>LeagueService</code> component.
	 * 
	 * @return
	 * 			the <code>LeagueService</code> component.
	 */
	public LeagueService getLeagueService() {
		return leagueService;
	}

	/**
	 * Returns the <code>MatchService</code> component.
	 * 
	 * @return
	 * 			the <code>MatchService</code> component.
	 */
	public MatchService getMatchService() {
		return matchService;
	}

	private UserService createUserService() {
		return new UserServiceImpl(users, leagues);
	}

	private LeagueService createLeagueService() {
		return new LeagueServiceImpl(userService, leagues);
	}

	private MatchService createMatchService() {
		return new MatchServiceImpl(matches);
	}

	/**
	 * Returns the value of the required attribute from the user represented by
	 * the id.
	 * 
	 * @param id
	 *            The id of the user.
	 * @param attribute
	 *            The attribute to be returned.
	 * @return the value of the required attribute from the user represented by
	 *         the id.
	 * @throws UnknownAttributeException 
	 * @throws UserDoesNotExistException 
	 */
	public Object getUserAttribute(final String id, final String attribute)
			throws UserDoesNotExistException, UnknownAttributeException {
		final User user = userService.getUser(id);
		ReflectionHelper.checkAttribute(user, attribute,
				BLMSMessages.UNKNOWN_USER_ATTRIBUTE_MESSAGE);
		return ReflectionHelper.getAttribute(user, attribute);
	}


	/**
	 * Changes the given attribute of the specified user to the specified value.
	 * 
	 * @param id
	 *            The id of the user.
	 * @param attribute
	 *            The attribute to be replaced.
	 * @param value
	 *            The new value of the attribute.
	 * @throws ValidationException 
	 * @throws UserDoesNotExistException 
	 * @throws UnknownAttributeException 
	 * @throws RestrictionException 
	 */
	public void changeUserAttribute(final String id, final String attribute,
			final String value) throws ValidationException, UserDoesNotExistException, UnknownAttributeException, RestrictionException{

		if (isBlankOrNull(attribute)) {
			throw new ValidationException(
					BLMSMessages.MUST_PROVIDE_ATTRIBUTE_MESSAGE);
		}
		final User user = userService.getUser(id);
		ReflectionHelper.checkAttribute(user, attribute,
				BLMSMessages.UNKNOWN_USER_ATTRIBUTE_MESSAGE);

		if (attribute.equals("picture")) {
			ReflectionHelper.setAttribute(user, attribute,
					(value != null) ? new File(value) : value);

		} else {
			if (attribute.equals("email") && userService.containsEmail(value)
					&& !user.getEmail().equals(value)) {
				throw new RestrictionException(
						BLMSMessages.USER_WITH_THIS_MAIL_EXISTS_MESSAGE);
			}

			final User newUser = new User(user.getFirstName(), user
					.getLastName(), user.getHomePhone(), user.getWorkPhone(),
					user.getCellPhone(), user.getEmail(), user.getPicture());
			newUser.setId(user.getId());
			ReflectionHelper.setAttribute(newUser, attribute, value);
			userService.updateUser(newUser);
		}
	}

	/**
	 * Returns the value of the required attribute from the league represented
	 * by the id.
	 * 
	 * @param id
	 *            The id of the league.
	 * @param attribute
	 *            The attribute to be returned.
	 * @return the value of the required attribute from the user represented by
	 *         the id.
	 * @throws UnknownAttributeException 
	 * @throws LeagueDoesNotExistException 
	 */
	public Object getLeagueAttribute(final Serializable id,
			final String attribute) throws LeagueDoesNotExistException, UnknownAttributeException {

		final League user = getLeagueService().getLeague(id);
		ReflectionHelper.checkAttribute(user, attribute,
				BLMSMessages.UNKNOWN_LEAGUE_ATTRIBUTE_MESSAGE);
		final Object attrValue = ReflectionHelper.getAttribute(user, attribute);
		return "creationDate".equals(attribute) ? formatDate((Date) attrValue)
				: attrValue;// FIXME:
	}

	// FIXME: CODE
	/**
	 * Changes the specified attribute of the league represented by the id.
	 * 
	 * @param id
	 *            the id of the league.
	 * @param attribute
	 *            the attribute to be changed.
	 * @param value
	 *            the new value of the attribute.
	 * @throws ValidationException 
	 * @throws LeagueDoesNotExistException 
	 * @throws UnknownAttributeException 
	 * @throws UserDoesNotExistException 
	 * @throws RestrictionException 
	 */
	public void changeLeagueAttribute(final String id, final String attribute,
			final String value) throws ValidationException, LeagueDoesNotExistException, UnknownAttributeException, UserDoesNotExistException, RestrictionException{

		if (isBlankOrNull(attribute)) {
			throw new ValidationException(
					BLMSMessages.MUST_PROVIDE_ATTRIBUTE_MESSAGE);
		}

		if ("name".equals(attribute)
				&& getLeagueService().containsLeagueWithName(value)) {
			throw new RestrictionException(
					BLMSMessages.LEAGUE_ALREADY_EXISTS_MESSAGE);
		}

		Object attrValue = value;
		final League league = getLeagueService().getLeague(id);
		ReflectionHelper.checkAttribute(league, attribute,
				BLMSMessages.UNKNOWN_LEAGUE_ATTRIBUTE_MESSAGE);

		if ("operator".equals(attribute)) {
			if (isBlankOrNull(value)) {
				throw new RestrictionException(BLMSMessages.REQUIRED_DATA_MESSAGE
						+ "league operator");
			}
			attrValue = userService.getUser(value);
		}

		final League newLeague = new League(league.getName(), league
				.getOperator(), league.getCreationDate());
		newLeague.setId(league.getId());

		ReflectionHelper.setAttribute(newLeague, attribute, attrValue);
		getLeagueService().updateLeague(newLeague);
	}

	/**
	 * Verifies whether the specified user is member of the specified league.
	 * 
	 * @param userID
	 *            The id of the user.
	 * @param leagueID
	 *            The id of the league.
	 * @return true if the user is a member of the league; false otherwise.
	 * @throws UserDoesNotExistException 
	 * @throws LeagueDoesNotExistException 
	 */
	public boolean isLeagueMember(final String userId,
			final Serializable leagueId) throws LeagueDoesNotExistException,
			UserDoesNotExistException {
		final User user = userService.getUser(userId);
		final League league = getLeagueService().getLeague(leagueId);
		return league.isMember(user);
	}

	/**
	 * Returns the leagues that the specified user is a member.
	 * 
	 * @param userId
	 *            The id of the user.
	 * @return the leagues that the specified user is a member.
	 * @throws UserDoesNotExistException
	 *             thrown if the user does not exist.
	 */
	public Iterable<League> getPlayerLeagues(final String userId)
			throws UserDoesNotExistException {
		return getLeagueService().getPlayerLeagues(userId);
	}

	/**
	 * Returns the members of the specified league.
	 * 
	 * @param idLeague
	 *            The id of the league.
	 * @return the members of the specified league.
	 * @throws LeagueDoesNotExistException
	 *             thrown if the league does not exist.
	 */
	public Iterable<User> getLeagueMembers(final Serializable leagueId)
			throws LeagueDoesNotExistException {
		final League league = getLeagueService().getLeague(leagueId);
		return league.getUsers();
	}

	/**
	 * Makes the user join the league with an initial handicap.
	 * 
	 * @param userID
	 *            the user id.
	 * @param leagueId
	 *            the league id.
	 * @param initialHandicap
	 *            the initial handicap.
	 * @throws UserDoesNotExistException 
	 * @throws LeagueDoesNotExistException 
	 * @throws RestrictionException 
	 * @throws ValidationException 
	 * @throws NumberFormatException 
	 */
	public void joinLeague(final String userId, final Serializable leagueId,
			final String initialHandicap) throws UserDoesNotExistException,
			LeagueDoesNotExistException, RestrictionException, NumberFormatException, ValidationException {

		if (isBlankOrNull(initialHandicap)) {
			throw new RestrictionException(BLMSMessages.MUST_PROVIDE_HANDICAP_MESSAGE);
		}

		final League league = getLeagueService().getLeague(leagueId);
		final User user = userService.getUser(userId);
		getLeagueService().join(league, user, Integer.parseInt(initialHandicap));
	}

	/**
	 * Removed the specified user from the specified league.
	 * 
	 * @param userID
	 *            the user id.
	 * @param leagueID
	 *            the league id.
	 * @throws LeagueDoesNotExistException 
	 * @throws UserDoesNotExistException 
	 * @throws RestrictionException 
	 */
	public void leaveLeague(final String userID, final Serializable leagueID)
			throws LeagueDoesNotExistException, UserDoesNotExistException, RestrictionException {

		final League league = getLeagueService().getLeague(leagueID);
		final User user = userService.getUser(userID);
		getLeagueService().leave(league, user);
	}

	/**
	 * Returns the user league attribute.
	 * 
	 * @param userId
	 *            the user id.
	 * @param leagueId
	 *            the league id
	 * @param attribute
	 *            the attribute.
	 * @return
	 * 			the user league attribute.
	 * @throws UserDoesNotExistException
	 * @throws LeagueDoesNotExistException
	 * @throws UnknownAttributeException
	 * @throws RestrictionException 
	 */
	public Object getUserLeagueAttribute(final String userId,
			final Serializable leagueId, final String attribute)
			throws UserDoesNotExistException, LeagueDoesNotExistException,
			UnknownAttributeException, RestrictionException {

		final User user = userService.getUser(userId);
		final League league = getLeagueService().getLeague(leagueId);

		if (!league.isMember(user)) {
			throw new RestrictionException(
					BLMSMessages.USER_IS_NOT_LEAGUE_MEMBER_MESSAGE);
		}

		final Joining joining = league.getJoiningFrom(user);
		ReflectionHelper.checkAttribute(joining, attribute,
				BLMSMessages.UNKNOWN_USER_ATTRIBUTE_MESSAGE);
		return ReflectionHelper.getAttribute(joining, attribute);
	}

	/**
	 * Returns the number of the matches of the league.
	 * 
	 * @param leagueId
	 *            the league id.
	 * @return the number of the matches of the league.
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 */
	public int getNumberOfMatches(final Serializable leagueId)
			throws LeagueDoesNotExistException {
		final League league = getLeagueService().getLeague(leagueId);
		return league.getMatches().size();
	}

	/**
	 * Returns the number of the matches of the league with the specified
	 * player.
	 * 
	 * @param playerId
	 *            the player id.
	 * @param leagueID
	 *            the league id.
	 * @return the number of the matches of the league with the specified
	 *         player.
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 * @throws UserDoesNotExistException
	 *             if the user does not exist.
	 */
	public int getNumberOfMatches(final Serializable playerId,
			final Serializable leagueId) throws LeagueDoesNotExistException,
			UserDoesNotExistException {
		final League league = getLeagueService().getLeague(leagueId);
		final User user = userService.getUser(playerId);
		return league.getMatchesFrom(user).size();
	}

	/**
	 * Adds a match result.
	 * 
	 * @param leagueId
	 * @param dateStr
	 *            the date of the match.
	 * @param winnerId
	 *            the winner of the match.
	 * @param loserId
	 *            the loser of the match.
	 * @return the match.
	 * @throws ParseException
	 * @throws ValidationException 
	 * @throws UserDoesNotExistException 
	 * @throws LeagueDoesNotExistException 
	 */
	public Serializable addMatchResult(final Serializable leagueId,
			final String dateStr, final String winnerId, final String loserId)
			throws ParseException, LeagueDoesNotExistException, UserDoesNotExistException, ValidationException {
		final int defaultValue = Integer.MIN_VALUE;
		return addMatchResult(leagueId, dateStr, winnerId, loserId,
				defaultValue, defaultValue, defaultValue, defaultValue);
	}

	/**
	 * Adds a match result.
	 * 
	 * @param leagueId
	 *            the league id.
	 * @param dateStr
	 *            the date of the match.
	 * @param winnerId
	 *            winnerID the winner of the match.
	 * @param loserId
	 *            loserID the loser of the match.
	 * @param length
	 *            the length of the match.
	 * @param score
	 *            the score of the match.
	 * @param longestRunForWinner
	 *            the longest run for the winner of the match.
	 * @param longestRunForLoser
	 *            the longest run for the loser of the match.
	 * @return
	 * @throws NumberFormatException
	 * @throws ParseException
	 * @throws RestrictionException 
	 * @throws ValidationException 
	 * @throws UserDoesNotExistException 
	 * @throws LeagueDoesNotExistException 
	 */
	public Serializable addMatchResult(final Serializable leagueId,
			final String dateStr, final String winnerId, final String loserId,
			final String length, final String score,
			final String longestRunForWinner, final String longestRunForLoser)
			throws NumberFormatException, ParseException, RestrictionException, LeagueDoesNotExistException, UserDoesNotExistException, ValidationException {
		validateInputFromMatchResult(length, score, longestRunForWinner,
				longestRunForLoser);
		return addMatchResult(leagueId, dateStr, winnerId, loserId, Integer
				.parseInt(length), Integer.parseInt(score), Integer
				.parseInt(longestRunForWinner), Integer
				.parseInt(longestRunForLoser));
	}

	private Serializable addMatchResult(final Serializable leagueId,
			final String dateStr, final String winnerId, final String loserId,
			final int length, final int score, final int longestRunForWinner,
			final int longestRunForLoser) throws ParseException, LeagueDoesNotExistException, UserDoesNotExistException, ValidationException {

		final League league = getLeagueService().getLeague(leagueId);
		final User winner = userService.getUser(winnerId);
		final User loser = userService.getUser(loserId);
		return matchService.createMatch(league, winner, loser,
				parseDate(dateStr), length, score, longestRunForWinner,
				longestRunForLoser).getId();
	}

	/**
	 * Returns the number of wins of the user in the league.
	 * 
	 * @param playerID
	 *            the player id.
	 * @param leagueID
	 *            the league id.
	 * @return the number of wins of the user in the league.
	 * @throws UserDoesNotExistException
	 *             if the user does not exist.
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 */
	public int getNumberOfWins(final String playerId,
			final Serializable leagueId) throws LeagueDoesNotExistException,
			UserDoesNotExistException {
		final User user = userService.getUser(playerId);
		final League league = getLeagueService().getLeague(leagueId);
		return league.getNumberOfWins(user);
	}

	/**
	 * Returns the number of losses of the user in the league.
	 * 
	 * @param playerID
	 *            the player id.
	 * @param leagueID
	 *            the league id.
	 * @return the number of losses of the user in the league.
	 * @throws UserDoesNotExistException
	 *             if the user does not exist.
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 */
	public int getNumberOfLosses(final String playerId,
			final Serializable leagueId) throws LeagueDoesNotExistException,
			UserDoesNotExistException {
		final User user = userService.getUser(playerId);
		final League league = getLeagueService().getLeague(leagueId);
		return league.getNumberOfLosses(user);
	}

	/**
	 * Returns the match in the league with the specified index.
	 * 
	 * @param leagueID
	 *            the league id.
	 * @param index
	 *            the index of the match.
	 * 
	 * @return the <code>Match</code>
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 */
	public Match getMatch(final Serializable leagueId, final int index)
			throws LeagueDoesNotExistException {

		final League league = getLeagueService().getLeague(leagueId);
		return league.getMatch(index);
	}

	/**
	 * Returns the match in the league with the specified index and player id.
	 * 
	 * @param playerID
	 *            the player id.
	 * @param leagueID
	 *            the league id.
	 * @param index
	 *            the index of the match.
	 * @return the match
	 * @throws UserDoesNotExistException
	 * @throws LeagueDoesNotExistException
	 */
	public Match getMatch(final String playerId, final Serializable leagueId,
			final int index) throws UserDoesNotExistException,
			LeagueDoesNotExistException {

		final League league = getLeagueService().getLeague(leagueId);
		final User user = userService.getUser(playerId);
		return league.getMatchFrom(user, index);
	}

	/**
	 * Returns the matches as a String representation.
	 * 
	 * @param playerID
	 *            the player id.
	 * @param leagueID
	 *            the league id.
	 * @param index
	 *            the index of the match.
	 * @return the match.
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 * @throws UserDoesNotExistException
	 *             if the user does not exist.
	 */
	public String getMatchAsString(final String playerId,
			final Serializable leagueId, final int index)
			throws UserDoesNotExistException, LeagueDoesNotExistException {
		final Match match = getMatch(playerId, leagueId, index);
		final boolean isWinner = playerId.equals(match.getWinner().getId());
		return isWinner ? match.getWinnerDescription() : match
				.getLoserDescription();
	}

	/**
	 * Returns the value of the required attribute from the match represented by
	 * the id.
	 * 
	 * @param matchId
	 *            The id of the match.
	 * @param attributeName
	 *            The attribute to be returned.
	 * @return the value of the required attribute from the user represented by
	 *         the id.
	 * @throws MatchDoesNotExistException
	 * @throws UnknownAttributeException 
	 */
	public Object getMatchAttribute(final Serializable matchId,
			final String attributeName) throws MatchDoesNotExistException, UnknownAttributeException{
		final Match match = matchService.getMatch(matchId);
		return ReflectionHelper.getAttribute(match, attributeName);
	}

	/**
	 * Returns the numeric value of the required attribute from the match represented by
	 * the id.
	 * 
	 * @param matchId
	 *            The id of the match.
	 * @param attributeName
	 *            The attribute to be returned.
	 * @return the numeric value of the required attribute from the user represented by
	 *         the id.
	 * @throws MatchDoesNotExistException
	 * @throws UnknownAttributeException
	 */
	public Object getMatchNumericAttribute(final Serializable matchId,
			final String attributeName) throws MatchDoesNotExistException, UnknownAttributeException{

		final Number number = (Number) getMatchAttribute(matchId, attributeName);
		final int integerValue = number.intValue();
		if ((Integer.MIN_VALUE == integerValue)
				|| (Integer.MAX_VALUE == integerValue)) {
			return "";
		}
		return number;
	}

	/**
	 * Returns the date of the match.
	 * 
	 * @param matchId
	 *            the id of the match.
	 * @return the date of the match. as a String.
	 * @throws MatchDoesNotExistException
	 *             if the match does not exist.
	 */
	public String getMatchDate(final Serializable matchId)
			throws MatchDoesNotExistException {
		final Match match = matchService.getMatch(matchId);
		return formatDate(match.getMatchDate());
	}

	/**
	 * Returns the match by date.
	 * 
	 * @param leagueId
	 *            the league id.
	 * @param startDate
	 *            the start date interval.
	 * @param endDate
	 *            the end date interval.
	 * @param index
	 *            the index of the match.
	 * @return the match.
	 * @throws LeagueDoesNotExistException
	 *             if the league does not exist.
	 * @throws ParseException
	 */
	public Match getMatchByDate(final Serializable leagueId,
			final String startDateStr, final String endDateStr, final int index)
			throws LeagueDoesNotExistException, ParseException {

		final League league = getLeagueService().getLeague(leagueId);
		final Date start = parseDate(startDateStr);
		final Date end = parseDate(endDateStr);
		return league.getMatchesBetween(start, end, index);
	}

	/**
	 * Returns the match by date.
	 * 
	 * @param playerId
	 *            the player id
	 * @param leagueId
	 *            the league id
	 * @param startDate
	 *            the start date interval.
	 * @param endDate
	 *            the end date interval
	 * @param index
	 *            the index of the match.
	 * @return the match
	 * @throws LeagueDoesNotExistException
	 * @throws UserDoesNotExistException
	 * @throws ParseException
	 */
	public Match getMatchByDate(final String playerId,
			final Serializable leagueId, final String startDateStr,
			final String endDateStr, final int index)
			throws LeagueDoesNotExistException, UserDoesNotExistException,
			ParseException {

		final League league = getLeagueService().getLeague(leagueId);
		final User player = userService.getUser(playerId);
		final Date start = parseDate(startDateStr);
		final Date end = parseDate(endDateStr);
		return league.getMatchesBetween(player, start, end, index);
	}

	/**
	 * Updates a match result.
	 * 
	 * @param matchId
	 *            the match id.
	 * @param date
	 *            the date of the match.
	 * @param winnerID
	 *            the winner of the match.
	 * @param loserID
	 *            the looser of the match.
	 * @param length
	 *            the length of the match.
	 * @param score
	 *            the score of the match.
	 * @param longestRunForWinner
	 *            the longest run for the winner of the match.
	 * @param longestRunForLoser
	 *            the longest run for the loser of the match.
	 * @throws ParseException 
	 * @throws MatchDoesNotExistException 
	 * @throws UserDoesNotExistException 
	 * @throws ValidationException 
	 * @throws RestrictionException 
	 */
	public void updateMatchResult(final Serializable matchId,
			final String dateStr, final String winnerId, final String loserId,
			final String length, final String score,
			final String longestRunForWinner, final String longestRunForLoser)
			throws ParseException, MatchDoesNotExistException, UserDoesNotExistException, ValidationException, RestrictionException {

		validateInputFromMatchResult(length, score, longestRunForWinner,
				longestRunForLoser);

		final Match match = matchService.getMatch(matchId);
		final User winner = userService.getUser(winnerId);
		final User loser = userService.getUser(loserId);
		final Date date = parseDate(dateStr);

		final Match newMatch = new Match.Builder(match.getLeague()).winner(
				winner, Integer.parseInt(longestRunForWinner)).loser(loser,
				Integer.parseInt(longestRunForLoser)).withScore(
				Integer.parseInt(score)).withLength(Integer.parseInt(length))
				.on(date);
		newMatch.setId(match.getId());
		matchService.updateMatch(newMatch);
	}

	private void validateInputFromMatchResult(final String length,
			final String score, final String longestRunForWinner,
			final String longestRunForLoser) throws RestrictionException {
		
		if (!isInteger(score)) {
			throw new RestrictionException(BLMSMessages.INVALID_SCORE);
		}
		if (!isInteger(length)) {
			throw new RestrictionException(BLMSMessages.INVALID_MATCH_LENGTH);
		}
		if (!isInteger(longestRunForLoser)) {
			throw new RestrictionException(BLMSMessages.INVALID_RUN);
		}
		if (!isInteger(longestRunForWinner)) {
			throw new RestrictionException(BLMSMessages.INVALID_RUN);
		}
	}

	/**
	 * Returns the player standing.
	 * 
	 * @param playerId
	 *            player id.
	 * @param leagueId
	 *            the league id.
	 *            
	 * @return the player standing.
	 * @throws UserDoesNotExistException 
	 * @throws LeagueDoesNotExistException 
	 */
	public Object getPlayerStanding(final String playerId,
			final Serializable leagueId) throws LeagueDoesNotExistException,
			UserDoesNotExistException {
		final League league = getLeagueService().getLeague(leagueId);
		final User player = userService.getUser(playerId);
		return league.getPlayerStanding(player);
	}

	/**
	 * Defines standing expressions.
	 * 
	 * @param leagueId
	 *            the league id.
	 * @param expression
	 *            the expression.
	 * @throws LeagueDoesNotExistException
	 *             thrown if the league does not exist.
	 * @throws ParseException
	 *             if the parse of the expression fails.
	 */
	public void defineStandingsExpression(final String leagueId,
			final String expression) throws LeagueDoesNotExistException,
			ParseException {
		
		final League league = getLeagueService().getLeague(leagueId);
		try {
			league.setExpression(new AntlrExpression(expression,
					LeagueContext.AVAILABLE_VARIABLES));
		} catch (SyntaxErrorException e) {
			throw new SyntaxErrorException(BLMSMessages.STANDING_EXPRESSION_SYNTAX_ERROR_MESSAGE);
		} catch (UnknownVariableException e) {
			throw new UnknownVariableException(BLMSMessages.UNKNOWN_STANDING_VARIABLE);
		}
	}

	/**
	 * Returns the today date.
	 * 
	 * @return the today date.
	 */
	public String today() {
		return formatDate(todayDate());
	}

	/**
	 * @return
	 */
	public Date todayDate() {
		return this.today;
	}

	/**
	 * Sets the date format for the system.
	 * 
	 * @param format
	 *            the format of the date.
	 */
	public void setDateFormat(final String format) {
		LocalizedDataFormat.setDateFormat(format);
	}

	private Date parseDate(final String dateStr) throws ParseException {

		try {
			return LocalizedDataFormat.parse(dateStr);
		} catch (final ParseException e) {
			throw new ParseException(BLMSMessages.INVALID_DATE_MESSAGE, e
					.getErrorOffset());

		} catch (final NullPointerException e) {
			throw new NullPointerException(BLMSMessages.INVALID_DATE_MESSAGE);
		}
	}

	private String formatDate(final Date date) {
		return LocalizedDataFormat.formatDate(date);
	}

	/**
	 * @param handicapStronger
	 * @param handicapWeaker
	 * @param scoreHigherRatedPlayer
	 * @return
	 * @throws RestrictionException
	 * @throws UserDoesNotExistException
	 */
	public int matchup(final String handicapStronger, final String handicapWeaker,
			final String scoreHigherRatedPlayer) throws RestrictionException, UserDoesNotExistException {
		
		if (ValidatorUtil.isBlankOrNull(handicapStronger) || ValidatorUtil.isBlankOrNull(handicapWeaker)) throw new RestrictionException(BLMSMessages.HANDICAP_MUST_BE_PROVIDED_MESSAGE);
		if (ValidatorUtil.isBlankOrNull(scoreHigherRatedPlayer)) throw new RestrictionException(BLMSMessages.MUST_PROVIDE_A_TABLE_ID_MESSAGE);
		
		int handicapStrongerValue = Integer.parseInt(handicapStronger);
		int handicapWeakerValue = Integer.parseInt(handicapWeaker);
		
		return getMatchupService().createMatchup(handicapStrongerValue, handicapWeakerValue, scoreHigherRatedPlayer).matchup();
	}

	/**
	 * @param playerId
	 * @param leagueId
	 * @return
	 * @throws UserDoesNotExistException
	 * @throws LeagueDoesNotExistException
	 */
	public int getPlayerHandicap(Serializable playerId, String leagueId) throws UserDoesNotExistException, LeagueDoesNotExistException {
		
		League league = getLeagueService().getLeague(leagueId);
		User user = getUserService().getUser(playerId);
		return league.getPlayerHandicap(user);
	}
	
	/**
	 * @param strongerMail
	 * @param weakerMail
	 * @param scoreHigherRatedPlayer
	 * @param leagueId
	 * @return
	 * @throws RestrictionException
	 * @throws UserDoesNotExistException
	 * @throws LeagueDoesNotExistException
	 */
	public int matchup(String strongerMail, String weakerMail,
			String scoreHigherRatedPlayer, String leagueId) throws RestrictionException, UserDoesNotExistException, LeagueDoesNotExistException {
		
		if (!getUserService().containsEmail(strongerMail) || !getUserService().containsEmail(weakerMail)) throw new RestrictionException(BLMSMessages.UNKNOWN_USER_MESSAGE);
		
		User strongerUser = getUserService().findUserByEmail(strongerMail);
		User weakerUser = getUserService().findUserByEmail(weakerMail);
		
		League league = getLeagueService().getLeague(leagueId);
		
		if ( !league.isMember(strongerUser) || !league.isMember(weakerUser) ) throw new RestrictionException(BLMSMessages.PLAYERS_ARE_NOT_AT_THE_SAME_LEAGUE_MESSAGE); 
		
		int handicapStrongerValue = getPlayerHandicap(strongerUser.getId(), leagueId);
		int handicapWeakerValue = getPlayerHandicap(weakerUser.getId(), leagueId);
		
		return matchup(handicapStrongerValue+"", handicapWeakerValue+"", scoreHigherRatedPlayer);
	}

	/**
	 * Defines handicap expressions.
	 * 
	 * @param leagueId
	 *            the league id.
	 * @param expression
	 *            the expression.
	 * @throws LeagueDoesNotExistException 
	 *             thrown if the league does not exist.
	 * @throws ParseException
	 *             if the parse of the expression fails.
	 */
	public void defineHandicapExpression(String leagueId, String expression) throws LeagueDoesNotExistException, ParseException {

		final League league = getLeagueService().getLeague(leagueId);
		try {
			league.setHandicapExpression(new AntlrExpression(expression,
					HandicapContext.HANDICAP_AVAILABLE_VARIABLES));
		} catch (SyntaxErrorException e) {
			throw new SyntaxErrorException(BLMSMessages.HANDICAP_EXPRESSION_SYNTAX_ERROR_MESSAGE);
		} catch (UnknownVariableException e) {
			throw new UnknownVariableException(BLMSMessages.UNKNOWN_HANDICAP_VARIABLE);
		}
	}

	/**
	 * Defines the handicap value for a player in a league.
	 * 
	 * @param playerId
	 * 			the player id.
	 * @param leagueId
	 * 			the league id.
	 * @param insertionDate
	 * 			manual handicap insetion date
	 * @param handicap
	 * 			the handicap value	
	 * @throws LeagueDoesNotExistException 
	 * 				thrown if the league does not exist.
	 * @throws UserDoesNotExistException 
	 * @throws ParseException 
	 */
	public void setPlayerHandicap(String playerId, String leagueId, String insertionDate, int handicap) throws LeagueDoesNotExistException, UserDoesNotExistException, ParseException {

		final League league = getLeagueService().getLeague(leagueId);
		final User user = getUserService().getUser(playerId);
		league.setPlayerHandicap(user, parseDate(insertionDate), handicap);
	}

	/**
	 * Gets the handicap modification history size.
	 * 
	 * @param playerId
	 * 			the player id.
	 * @param leagueId
	 * 			the league id. 
	 * @return
	 * @throws LeagueDoesNotExistException 
	 * 			thrown if the league does not exist.
	 * @throws UserDoesNotExistException
	 * 			thrown if the user does not exist. 
	 */
	public int getHandicapHistorySize(String playerId, String leagueId) throws LeagueDoesNotExistException, UserDoesNotExistException {
		
		final League league = getLeagueService().getLeague(leagueId);
		final User user = getUserService().getUser(playerId);
		return league.getHandicapHistorySize(user);
	}

	/**
	 * Gets the handicap modification history last update.
	 * 
	 * @param playerId
	 * 			the player id.
	 * @param leagueId
	 *  		the league id.
	 * @return
	 * 			the handicap modification history last update
	 * @throws LeagueDoesNotExistException 
	 * @throws UserDoesNotExistException 
	 */
	public String getHandicapHistoryLastUpdate(String playerId, String leagueId) throws LeagueDoesNotExistException, UserDoesNotExistException {
		
		final League league = getLeagueService().getLeague(leagueId);
		final User user = getUserService().getUser(playerId);
		Date handicapHistoryLastUpdate = league.getHandicapHistoryLastUpdate(user);
		return formatDate(handicapHistoryLastUpdate);
	}
}